/**
 *
 */
package com.angel.io.processors.runners.imports.impl;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.angel.common.helpers.StringHelper;
import com.angel.io.daos.ProcessorLogDAO;
import com.angel.io.descriptor.FileProcessorDescriptor;
import com.angel.io.exceptions.InvalidRowDataException;
import com.angel.io.log.ProcessorLog;
import com.angel.io.parameters.ParametersService;
import com.angel.io.processors.commands.imports.ImportFileProcessorCommand;
import com.angel.io.processors.commands.imports.ImportRowProcessorCommand;
import com.angel.io.processors.runners.imports.ImportProcessorRunner;
import com.angel.io.type.cols.HeaderColumnFile;
import com.angel.io.type.rows.ImportRowFile;
import com.angel.io.type.rows.impl.HeaderRowFile;

/**
 *	@author William
 *	@since 15/January/2009.
 *
 */
public class ImportFileProcessorRunner implements ImportProcessorRunner{

	private static final Logger LOGGER = Logger.getLogger(ImportFileProcessorRunner.class);

	private FileProcessorDescriptor fileProcessorDescriptor;
	private HeaderRowFile headerRow;
	private ParametersService parametersService;
	private List<HeaderColumnFile> temporaryHeaderColumns = new ArrayList<HeaderColumnFile>();;
	private ImportFileProcessorCommand importFileCommand;
	private ImportRowProcessorCommand importRowCommand;
	private ProcessorLogDAO processorLogDAO;

	public ImportFileProcessorRunner(FileProcessorDescriptor fileProcessorDescriptor, ImportFileProcessorCommand importFileCommand, ImportRowProcessorCommand importRowCommand){
		super();
		//TODO Verify if necessary this file processor descriptor.
		this.fileProcessorDescriptor = fileProcessorDescriptor;
		this.headerRow = new HeaderRowFile();
		this.parametersService = new ParametersService();
		this.importFileCommand = importFileCommand;
		this.importRowCommand = importRowCommand;
	}

	protected ImportFileProcessorRunner(FileProcessorDescriptor fileProcessorDescriptor, ImportFileProcessorCommand importFileCommand){
		super();
		//TODO Verify if necessary this file processor descriptor.
		this.fileProcessorDescriptor = fileProcessorDescriptor;
		this.headerRow = new HeaderRowFile();
		this.parametersService = new ParametersService();
		this.importFileCommand = importFileCommand;
	}

	/**
	 * 	Process import rows in a page.
	 *
	 * @param <T> entities type to process.
	 * @param rowFiles to process in a page.
	 * @return all entities processed in page.
	 */
	protected <T> List<T> processRowsInPage(List<ImportRowFile> rowFiles, Integer numberPage) {
		ProcessorLog processorLog = this.fileProcessorDescriptor.getProcessorLog();
		processorLog.setNumberPage(numberPage);
		List<T> objects = new ArrayList<T>();
		if(rowFiles != null && rowFiles.size() > 0){
			long rowNumber = 0;
			LOGGER.debug("Current page has header [" + this.getFileProcessorDescriptor().getHasHeader() + "].");
			if(this.fileProcessorDescriptor.getHasHeader()){
				this.processHeaderRowFile(rowFiles.get(0), processorLog);
				rowFiles.remove(0);
				rowNumber++;
			}
			for(ImportRowFile rowFile: rowFiles){
				try {
					T entity = this.processEachRow(rowFile, processorLog, rowNumber);
					objects.add(entity);
					rowNumber++;
				} catch(InvalidRowDataException e){
					processorLog.addFailRowMessageLog(Long.valueOf(rowNumber), e.getMessage());
					LOGGER.error("Invalid data [" + rowFile.toString() + "] at row number [" + rowNumber + "] caused by: " + e.getMessage(), e);
				}
			}
			this.persistProcessorLog(processorLog);
		}
		return objects;
	}
	
	protected void persistProcessorLog(ProcessorLog processorLog){
		LOGGER.info("Persisting processor log for page number [" + processorLog.getNumberPage() + "] with rows messages [" + processorLog.countRowMessages() + "].");
		if(this.hasProcessorLogDAO()){
			this.getProcessorLogDAO().persistOrUpdate(processorLog);
		}
	}

	/**
	 * Process each row in current page. First it checks row data with {@link ImportRowProcessorCommand#checkRowData(ImportRowFile, HeaderRowFile)}.
	 * If row data is valid, then it process' row data with {@link ImportRowProcessorCommand#processRow(ImportRowFile, HeaderRowFile, ParametersService)}.
	 * Finally, if row data was processed correctly, it adds a 'Processed OK' message to processor log. If it fails, it adds an error
	 * message in processor log, and it logs error in log file.
	 *
	 * @param <T> row entity type.
	 * @param rowFile to process in a page.
	 * @param processorLog to log each events.
	 * @param rowNumber processing at this moment.
	 * @return if row is process OK, it returns row entity. Otherwise it throws {@link InvalidRowDataException}.
	 */
	protected <T extends Object> T processEachRow(ImportRowFile rowFile, ProcessorLog processorLog, Long rowNumber){
		LOGGER.info("Checking DATA for row file number [" + rowNumber + "] with data [" + rowFile.toString() + "].");
		this.importRowCommand.checkRowData(rowFile, this.headerRow);

		LOGGER.info("Processing DATA for row file number [" + rowNumber + "] with data [" + rowFile.toString() + "].");
		T entity = this.importRowCommand.processRow(rowFile, this.headerRow, this.parametersService);

		processorLog.addProcessedRowMessageLog(Long.valueOf(rowNumber), "OK");

		return entity;
	}

	/**
	 * Process header row file.
	 *
	 * @param headerRowFile current header in a page.
	 * @param processorLog to log events.
	 */
	protected void processHeaderRowFile(ImportRowFile headerRowFile, ProcessorLog processorLog){
		try {
			LOGGER.info("Processing HEADER row file with [" + headerRowFile.quantityColumns() + "] columns. Values [" + this.headerRow.toString() + "].");
			this.updateHeaderRow(headerRowFile);

			String[] headerColumnsNames = this.importRowCommand.getHeaderColumnsNames();

			LOGGER.debug("HEADER has [" + headerRowFile.quantityColumns() + "] columns. Its names are [" + StringHelper.convertToPlainString(headerColumnsNames) + "].");

			boolean hasAllColumns = this.headerRow.hasAllColumnName(headerColumnsNames);

			if(!hasAllColumns){
				throw new InvalidRowDataException("Header is invalid. " + this.importRowCommand.getClass().getSimpleName() + " expects have columns names [" + StringHelper.convertToPlainString(headerColumnsNames) + "].");
			}
		} catch(InvalidRowDataException e){
			processorLog.addFailRowMessageLog(Long.valueOf(0), e.getMessage());
			LOGGER.error("Invalid HEADER data [" + headerRowFile.toString() + "] caused by: " + e.getMessage());
		}
	}


	/* (non-Javadoc)
	 * @see com.angel.io.processors.runner.interfaces.ProcessorRunner#runProcess(java.io.InputStream)
	 */
	public <T extends Object> List<T> runProcess(InputStream inputStream, Integer... pages) throws InvalidRowDataException {
		LOGGER.info("Initialize file processor...");
		this.importFileCommand.initialize(inputStream);

		if(pages == null || pages.length == 0){
			pages = new Integer[]{0};
		}

		List<T> allObjects = new ArrayList<T>();
		for(Integer numberPage: pages){
			this.clearHeaderRowFile();
			LOGGER.info("Begin processing page [" + numberPage + "] with file processor...");
			List<ImportRowFile> rowFiles = this.importFileCommand.processFile(numberPage);
			LOGGER.info("File processor processed [" + rowFiles.size() + "] row files for page number [" + numberPage + "].");
			List<T> objects = this.processRowsInPage(rowFiles, numberPage);
			LOGGER.info("Row processor processed [" + objects.size() + "] entities for page number [" + numberPage + "].");
			allObjects.addAll(objects);
		}
		LOGGER.info("Finish file processor...");
		this.importFileCommand.finish();

		LOGGER.info("Import processor runner processed [" + allObjects.size() + "] entities.");
		return allObjects;
	}

	protected void clearHeaderRowFile() {
		this.headerRow.removeColumns();
	}

	protected void updateHeaderRow(ImportRowFile rowFile) {
		/** Add columns to header row. */
		for(int i = 0; i < rowFile.quantityColumns(); i++){
			this.headerRow.addColumn(i, rowFile.getStringValue(i));
		}
		/** Update columns alias. */
		for(HeaderColumnFile col: this.temporaryHeaderColumns){
			this.headerRow.addAllAliasFor(col.getColumnName(), col.getAlias());
		}
	}

	public void addAliasFor(String columnName, String aliasName) {
		HeaderColumnFile col = this.findHeaderColumnFile(columnName);
		col.addAliasName(aliasName);
	}

	private HeaderColumnFile findHeaderColumnFile(String columnName){
		for(HeaderColumnFile col: this.temporaryHeaderColumns){
			if(col.getColumnName().equalsIgnoreCase(columnName)){
				return col;
			}
		}
		HeaderColumnFile newCol = new HeaderColumnFile(columnName);
		this.temporaryHeaderColumns.add(newCol);
		return newCol;
	}


	public void addAliasFor(String columnName, List<String> aliasNames) {
		for(String alias: aliasNames){
			this.addAliasFor(columnName, alias);
		}
	}

	public String findParameter(Object parameter){
		return this.parametersService.findParameter(parameter);
	}

	public Object findParameter(String name){
		return this.parametersService.findParameter(name);
	}

	public void removeParameter(String name){
		this.parametersService.removeParameter(name);
	}

	public boolean containsParameter(Object parameter){
		return this.parametersService.containsParameter(parameter);
	}

	public boolean containsParameter(String name){
		return this.parametersService.containsParameter(name);
	}

	public void addParameter(String name, Object parameter){
		this.parametersService.addParameter(name, parameter);
	}

	/**
	 * @return the importFileCommand
	 */
	public ImportFileProcessorCommand getImportFileCommand() {
		return importFileCommand;
	}

	/**
	 * @return the importRowCommand
	 */
	public Object getImportRowCommand() {
		return importRowCommand;
	}

	/**
	 * @return the fileProcessorDescriptor
	 */
	protected FileProcessorDescriptor getFileProcessorDescriptor() {
		return fileProcessorDescriptor;
	}

	/**
	 * @return the headerRow
	 */
	protected HeaderRowFile getHeaderRow() {
		return headerRow;
	}

	/**
	 * @return the parametersService
	 */
	protected ParametersService getParametersService() {
		return parametersService;
	}

	/**
	 * @return the temporaryHeaderColumns
	 */
	protected List<HeaderColumnFile> getTemporaryHeaderColumns() {
		return temporaryHeaderColumns;
	}

	/**
	 * @param fileProcessorDescriptor the fileProcessorDescriptor to set
	 */
	protected void setFileProcessorDescriptor(
			FileProcessorDescriptor fileProcessorDescriptor) {
		this.fileProcessorDescriptor = fileProcessorDescriptor;
	}

	/**
	 * @param headerRow the headerRow to set
	 */
	protected void setHeaderRow(HeaderRowFile headerRow) {
		this.headerRow = headerRow;
	}

	/**
	 * @param parametersService the parametersService to set
	 */
	protected void setParametersService(ParametersService parametersService) {
		this.parametersService = parametersService;
	}

	/**
	 * @param temporaryHeaderColumns the temporaryHeaderColumns to set
	 */
	protected void setTemporaryHeaderColumns(
			List<HeaderColumnFile> temporaryHeaderColumns) {
		this.temporaryHeaderColumns = temporaryHeaderColumns;
	}

	/**
	 * @param importFileCommand the importFileCommand to set
	 */
	protected void setImportFileCommand(ImportFileProcessorCommand importFileCommand) {
		this.importFileCommand = importFileCommand;
	}

	/**
	 * @param importRowCommand the importRowCommand to set
	 */
	protected void setImportRowCommand(ImportRowProcessorCommand importRowCommand) {
		this.importRowCommand = importRowCommand;
	}

	/**
	 * @return the processorLogDAO
	 */
	public ProcessorLogDAO getProcessorLogDAO() {
		return processorLogDAO;
	}

	/**
	 * @param processorLogDAO the processorLogDAO to set
	 */
	public void setProcessorLogDAO(ProcessorLogDAO processorLogDAO) {
		this.processorLogDAO = processorLogDAO;
	}
	
	protected boolean hasProcessorLogDAO(){
		return this.getProcessorLogDAO() != null;
	}
}
